﻿// Inner Fire 游戏引擎库
// DirectX主类分装 - 初始化
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 用于初始化的项目
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-24

#include <dx_graphics.h>
#include <dx_scene/dx_scene.h>

namespace ifire {

void DxGraphics::InitDirect3D(HWND hwnd, int width, int height) {
  hwnd_ = hwnd;
  client_width_ = width;
  client_height_ = height;

  CreateDevice();
  CreateFence();
  CheckMsaa();
  CreateCommandObjects();
  CreateSwapChain();
  CreateRtvAndDsvDescriptorHeaps();

  // 这是阴影贴图里面使用的。
  // 由于我们知道场景是如何构建的，因此手动估算场景的边界球。
  // 网格是“最宽的物体”，宽度为20，深度为30.0f，并且以世界空间原点为中心。
  // 通常情况下，您需要遍历每个世界空间顶点位置并计算边界球。
  mSceneBounds.Center = XMFLOAT3(0.0f, 0.0f, 0.0f);
  mSceneBounds.Radius = sqrtf(10.0f * 10.0f + 15.0f * 15.0f);

  dxutils::log("Init DirectX3D complete.");
}

void DxGraphics::CreateDevice() {
  // 启用调式层
#if defined(DEBUG) || defined(_DEBUG)
  ComPtr<ID3D12Debug> debugController;
  HASSERT(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
  debugController->EnableDebugLayer();
#endif

  // 得到工厂
  HASSERT(CreateDXGIFactory1(IID_PPV_ARGS(&factory_)));

  // 尝试创建硬件设备
  HRESULT hardware_result =
      D3D12CreateDevice(nullptr, // nullptr = 使用默认适配器
          D3D_FEATURE_LEVEL_11_0, // 最低的支持，如果达不到这个要求，则失败
          IID_PPV_ARGS(&device_));

  // 如果失败，则变为WARP设备
  if (FAILED(hardware_result)) {
    HASSERT(factory_->EnumWarpAdapter(IID_PPV_ARGS(&warp_adapter_)));
    HASSERT(D3D12CreateDevice(
        warp_adapter_.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device_)));
  }

  KLOG("Create device success!");
}

void DxGraphics::CreateFence() {
  // 创建Fence
  HASSERT(
      device_->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence_)));
  // 需要得到每个描述符的大小
  rtv_descriptor_size_ =
      device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
  dsv_descriptor_size_ =
      device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
  cbv_srv_uav_descriptor_size_ = device_->GetDescriptorHandleIncrementSize(
      D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}

void DxGraphics::CheckMsaa() {
  D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS quality_levels = {};
  quality_levels.Format = back_buffer_format_;
  quality_levels.SampleCount = 4;
  quality_levels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
  quality_levels.NumQualityLevels = 0;
  HASSERT(device_->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
      &quality_levels, sizeof(quality_levels)));
  // 得到项目
  msaa_quality_ = quality_levels.NumQualityLevels;
  assert(msaa_quality_ > 0 && "Unexpected MASS quality level.");

  KLOG("4xMASS quality: %d", msaa_quality_);
}

void DxGraphics::CreateCommandObjects() {
  // 创建命令队列（使用Direct模式）
  D3D12_COMMAND_QUEUE_DESC queue_desc = {};
  queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
  queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
  HASSERT(
      device_->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&command_queue_)));

  // 创建命令分配器
  HASSERT(device_->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
      IID_PPV_ARGS(command_allocator_.GetAddressOf())));

  // 创建命令列表
  HASSERT(device_->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
      command_allocator_.Get(), nullptr,
      IID_PPV_ARGS(command_list_.GetAddressOf())));

  // 要把命令列表置于关闭状态。因为第一次引用命令列表时，需要对其重置。调用重置方法之前又需要将其关闭。
  command_list_->Close();

  KLOG("Create command list success.");
}

void DxGraphics::CreateSwapChain() {
  // 需要释放之前的交换链，然后进行重建（在分辨率改变的时候会重建）
  swap_chain_.Reset();

  // 创建SwapChain
  DXGI_SWAP_CHAIN_DESC desc = {};
  desc.BufferDesc.Width = client_width_;
  desc.BufferDesc.Height = client_height_;
  desc.BufferDesc.RefreshRate.Numerator = 60;
  desc.BufferDesc.RefreshRate.Denominator = 1;
  desc.BufferDesc.Format = back_buffer_format_;
  desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
  desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

  // 设置MASS
  desc.SampleDesc.Count = GetMsaaSampleCount();
  desc.SampleDesc.Quality = GetMsaaSampleQuality();
  desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  desc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
  desc.OutputWindow = hwnd_;
  desc.Windowed = true;
  desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
  desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

  // 交换链需要命令队列对其刷新
  HASSERT(factory_->CreateSwapChain(
      command_queue_.Get(), &desc, swap_chain_.GetAddressOf()));

  KLOG("Create swapchain successed.");
}

void DxGraphics::CreateRtvAndDsvDescriptorHeaps() {
  if (scene_->IsTakeoverRtvAndDsv()) {
    scene_->CreateRtvAndDsvDescriptorHeaps();
    return;
  }

  // 创建渲染目标视图资源
  D3D12_DESCRIPTOR_HEAP_DESC rtv_desc = {};
  if (scene_->ID() == DxSceneID::DYNAMIC_CUBE) {
    rtv_desc.NumDescriptors = SWAP_CHAIN_BUFFER_COUNT + 6;
  } else {
    // 这里进行了+1，是用于离屏纹理。（离屏纹理+1，用于模糊）
    rtv_desc.NumDescriptors = SWAP_CHAIN_BUFFER_COUNT + 1;
  }
  rtv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
  rtv_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
  rtv_desc.NodeMask = 0;
  HASSERT(device_->CreateDescriptorHeap(
      &rtv_desc, IID_PPV_ARGS(rtv_heap_.GetAddressOf())));

  // 创建深度/模板视图资源
  D3D12_DESCRIPTOR_HEAP_DESC dsv_desc = {};
  if (scene_->ID() == DxSceneID::NORMAL) {
    dsv_desc.NumDescriptors = 1;
  } else if (scene_->ID() == DxSceneID::SHADOWS) {
    // 阴影贴图需要+1
    dsv_desc.NumDescriptors = 2;
  } else {
    dsv_desc.NumDescriptors = SWAP_CHAIN_BUFFER_COUNT;
  }
  dsv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
  dsv_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
  dsv_desc.NodeMask = 0;
  HASSERT(device_->CreateDescriptorHeap(
      &dsv_desc, IID_PPV_ARGS(dsv_heap_.GetAddressOf())));

  // 用于动态天空盒
  if (scene_->ID() == DxSceneID::DYNAMIC_CUBE) {
    mCubeDSV = CD3DX12_CPU_DESCRIPTOR_HANDLE(
        dsv_heap_->GetCPUDescriptorHandleForHeapStart(), 1,
        dsv_descriptor_size_);
  }

  KLOG("Create descriptor heaps successed.");
}

void DxGraphics::OnResize() {
  assert(device_);
  assert(swap_chain_);
  assert(command_allocator_);

  // 在进行资源更改之前，需要将队列清空
  FlushCommandQueue();

  // 重置命令队列
  HASSERT(command_list_->Reset(command_allocator_.Get(), nullptr));

  // 清空SwapChain，以进行重新创建
  for (UINT i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++) {
    swap_chain_buffer_[i].Reset();
  }
  depth_stencil_buffer_.Reset();

  // 重新设置SwapChainBuffer的大小
  HASSERT(swap_chain_->ResizeBuffers(SWAP_CHAIN_BUFFER_COUNT, client_width_,
      client_height_, back_buffer_format_,
      DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));
  current_back_buffer_ = 0;

  // 为每一个SwapChain创建RTV视图
  CreateRenderTargetView();
  // 创建DSV视图
  CreateDepthStencilView();

  // 执行Resize命令
  HASSERT(command_list_->Close());
  ID3D12CommandList* cmdsLists[] = {command_list_.Get()};
  command_queue_->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

  // 等待命令执行完成
  FlushCommandQueue();

  // 更新视口与剪裁
  UpdateViewport();

  // 更新模糊
  if (blur_filter_ != nullptr) {
    blur_filter_->OnResize(client_width_, client_height_);
  }

  // 更新相机
  camera_.SetLens(0.25f * MathHelper::Pi, GetAspectRatio(), 1.0f, 1000.0f);
  // 更新视椎体
  BoundingFrustum::CreateFromMatrix(camera_frustum_, camera_.GetProj());
  // 更新SSAO
  if (mSsao != nullptr) {
    mSsao->OnResize(mClientWidth, mClientHeight);
    // Resources changed, so need to rebuild descriptors.
    mSsao->RebuildDescriptors(mDepthStencilBuffer.Get());
  }
}

void DxGraphics::CreateRenderTargetView() {
  // 得到Handle
  CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle(
      rtv_heap_->GetCPUDescriptorHandleForHeapStart());

  // 需要为每一个SwapChain创建BUFFER
  for (UINT i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++) {
    // 获得交换链中的第i个缓冲区
    HASSERT(swap_chain_->GetBuffer(i, IID_PPV_ARGS(&swap_chain_buffer_[i])));
    // 为此缓冲区创建一个RTV
    device_->CreateRenderTargetView(
        swap_chain_buffer_[i].Get(), nullptr, rtv_heap_handle);
    // 偏移到下一个缓冲区
    rtv_heap_handle.Offset(1, rtv_descriptor_size_);
  }

  KLOG("Create swapchain render target view successed.");
}

void DxGraphics::CreateDepthStencilView() {
  // 配置缓冲区DESC
  D3D12_RESOURCE_DESC depth_stencil_desc = {};
  depth_stencil_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
  depth_stencil_desc.Alignment = 0;
  depth_stencil_desc.Width = client_width_;
  depth_stencil_desc.Height = client_height_;
  depth_stencil_desc.DepthOrArraySize = 1;
  depth_stencil_desc.MipLevels = 1;
  depth_stencil_desc.Format = depth_stencil_format_;
  depth_stencil_desc.SampleDesc.Count = GetMsaaSampleCount();
  depth_stencil_desc.SampleDesc.Quality = GetMsaaSampleQuality();
  depth_stencil_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
  depth_stencil_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

  // 清除资源的优化值（如果选择适当的清除值，可以提高操作的速度）
  D3D12_CLEAR_VALUE opt_clear = {};
  opt_clear.Format = depth_stencil_format_;
  opt_clear.DepthStencil.Depth = 1.0f;
  opt_clear.DepthStencil.Stencil = 0;

  // 创建资源并提交到堆
  CD3DX12_HEAP_PROPERTIES props(D3D12_HEAP_TYPE_DEFAULT);
  HASSERT(device_->CreateCommittedResource(&props, // 提交到默认堆
      D3D12_HEAP_FLAG_NONE, &depth_stencil_desc,
      D3D12_RESOURCE_STATE_COMMON, // 初始状态
      &opt_clear,                  // 清理资源优化值
      IID_PPV_ARGS(depth_stencil_buffer_.GetAddressOf())));

  // 利用此资源的格式，为整个资源的第0 mip层创建描述符
  device_->CreateDepthStencilView(
      depth_stencil_buffer_.Get(), nullptr, GetDepthStencilView());

  // 将资源从初始状态转换为深度缓冲区
  auto barrier =
      CD3DX12_RESOURCE_BARRIER::Transition(depth_stencil_buffer_.Get(),
          D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE);
  command_list_->ResourceBarrier(1, &barrier);

  KLOG("Create depth stencil view successed.");
}

void DxGraphics::UpdateViewport() {
  screen_viewport_.TopLeftX = 0;
  screen_viewport_.TopLeftY = 0;
  screen_viewport_.Width = static_cast<float>(client_width_);
  screen_viewport_.Height = static_cast<float>(client_height_);
  screen_viewport_.MinDepth = 0.0f;
  screen_viewport_.MaxDepth = 1.0f;

  scissor_rect_ = {0, 0, client_width_, client_height_};
}

HRESULT DxGraphics::LoadDDS(_In_z_ const char* szFileName,
    _Out_ ComPtr<ID3D12Resource>& texture,
    _Out_ ComPtr<ID3D12Resource>& textureUploadHeap) {
  return DirectX::CreateDDSTextureFromFile12(device_.Get(), command_list_.Get(),
      AnsiToWString(szFileName).c_str(), texture, textureUploadHeap);
}

} // namespace ifire